Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

46장. 두 언어가 공존하는 기간 — 라우팅 · 데이터 · 운영

45장의 마지막 완료 조건이 이것이었다.

구 코드 삭제하지 않음

이관은 배포가 아니라 전환이다.

그리고 전환에는 두 시스템이 함께 도는 기간이 있다.

⚠️ 이 기간은 대개 예상보다 길다.

도메인 하나에 몇 달,
전체로는 몇 년이 걸린다.

그러니 이 기간 자체를 설계해야 한다.


전환의 세 상태

flowchart LR
    A[구 시스템만] --> B[구 처리 + 새 시스템 그림자]
    B --> C[새 처리 + 구 시스템 대기]
    C --> D[새 시스템만]

45장의 섀도 트래픽이 B 다.

C 가 이 장의 주제다.
실제 처리가 넘어갔지만 되돌릴 수 있는 상태.

D 로 가는 판단은 마지막에 다룬다.


라우팅 — 무엇을 기준으로 넘기는가

전환 단위가 곧 롤백 단위다.

기준장점주의
엔드포인트별되돌리기 쉽다한 화면이 두 시스템을 부를 수 있다
사용자 비율점진적 확대같은 사용자가 같은 쪽으로 가야 한다
내부/외부내부부터 안전하게트래픽 특성이 다르다
읽기/쓰기읽기부터쓰기 전환이 진짜 관문이다

🔥 실무에서는 조합해서 쓴다.

1주차   읽기 API, 내부 직원만
2주차   읽기 API, 전체 사용자 5%
4주차   읽기 API, 전체
6주차   쓰기 API, 내부 직원만
...

⚠️ 사용자 비율로 나눌 때 고정 배정이 필요하다.

❌ 요청마다 랜덤 5%
   → 같은 사용자가 두 시스템을 오간다
   → 캐시·세션 불일치

✅ userId 해시 기준 고정
   → 한번 새 시스템이면 계속 새 시스템

전환은 게이트웨이나 리버스 프록시에서 한다.
애플리케이션 코드에 분기를 넣지 않는다.

코드에 넣으면 되돌릴 때도 배포가 필요해진다.


같은 DB를 두 시스템이 쓴다

계획대로 DB는 그대로 둔다.
43장에서 조인을 끊고 쓰기 주체를 모아둔 상태다.

그런데 이제 그 쓰기 주체가 두 시스템에 걸쳐 있다.

point_balances
  구 시스템 PointService     쓰기 ⚠️
  새 시스템 PointCommandService 쓰기 ⚠️

규칙이 필요하다.

쓰기는 한쪽만

## 공존 기간 규칙

- 한 테이블의 쓰기는 항상 한 시스템만 담당한다
- 전환은 테이블 단위로 하고, 전환 시점을 문서에 기록한다
- 읽기는 양쪽 모두 허용

⚠️ 이 규칙이 없으면 낙관적 락이 무력해진다.

두 ORM이 각자의 방식으로 버전 컬럼을 다루면
서로의 갱신을 덮어쓴다.

스키마 변경 권한

- 공존 중인 테이블의 스키마 변경은 새 시스템 쪽에서 관리한다
- 변경 시 양쪽 모두에서 동작하는지 확인한다 (27장의 확장-수축)
- 구 시스템 전용 컬럼은 새 시스템 엔티티에 매핑하지 않는다

27장에서 배포 중간 상태를 고려하라고 했는데,
여기서는 그 중간 상태가 몇 달간 지속된다.

컬럼을 추가할 때 구 시스템이 SELECT * 를 쓰고 있으면
예상치 못한 곳이 깨질 수 있다.

구 시스템에서 이 테이블을 SELECT * 로 조회하는 코드를 전부 찾아줘.
컬럼을 명시하지 않은 쿼리 전부.

37장의 문자열 추출이 여기서 또 쓰인다.


세션과 인증

두 시스템이 같은 사용자를 인식해야 한다.

방식공존 난이도
무상태 토큰 (JWT 등)🔥 쉽다. 서명 키만 공유
중앙 세션 저장소 (Redis)중간. 직렬화 형식이 문제
서버 로컬 세션어렵다. 먼저 걷어내야 한다

⚠️ 세 번째면 이관보다 이것부터 해야 한다.

두 번째도 함정이 있다.

구 시스템이 세션을 자체 직렬화 형식으로 저장하면
새 시스템이 읽지 못한다.

이관 대상 도메인을 고를 때
인증 방식을 먼저 확인하는 이유다.


롤백 경로를 리허설한다

45장의 완료 조건에 있던 항목이다.

## 롤백 절차

1. 게이트웨이 라우팅을 구 시스템으로 되돌린다 (1분)
2. 새 시스템이 쓴 데이터를 확인한다
   - 구 시스템이 읽을 수 있는 형태인가
   - 새 시스템만 쓰는 컬럼이 있으면 무시되는가
3. 진행 중이던 비동기 작업을 확인한다
4. 사용자 영향 확인 (전환 중 요청이 실패했는가)

🔥 2번이 실제로 어렵다.

새 시스템이 며칠간 쓴 데이터가 있는데,
구 시스템이 그것을 이해하지 못하면 롤백이 반쪽이 된다.

그래서 공존 기간에는 새 시스템이 새 컬럼을 만들지 않는다.

- 공존 기간에는 스키마를 확장하지 않는다
- 새 기능이 새 컬럼을 요구하면, 이관 완료 후로 미룬다

⚠️ 이 규칙이 이관을 서두르게 만드는 압력이 된다.
그것이 의도된 효과다.

공존 기간이 길어질수록 비용이 쌓인다.


두 코드베이스의 하네스

이 책의 관점에서 중요한 부분이다.

레포가 둘이면 CLAUDE.md 도 둘이다.
그리고 내용이 달라야 한다.

# legacy/CLAUDE.md

⚠️ 이 시스템은 이관 중이다. 새 기능을 여기에 만들지 않는다.

- 버그 수정과 장애 대응만 한다
- 리팩터링하지 않는다 (곧 삭제될 코드다)
- 새 테이블·컬럼을 만들지 않는다
- 이관된 도메인(point)의 코드는 수정하지 않는다
  수정이 필요하면 새 시스템 쪽에 요청한다
# service/CLAUDE.md

이 시스템은 이관 대상 도메인을 담당한다.

- 이관된 도메인: point (2026-09~)
- 미이관 도메인은 legacy API 를 호출한다
- 공존 기간 규칙은 docs/coexistence.md 참고

🔥 구 시스템 쪽 규칙이 특히 중요하다.

Agent에게 구 코드 수정을 시키면
“이왕 하는 김에” 정리하려 든다.

곧 삭제될 코드를 정리하는 것은 낭비다.
39장의 “정리를 위한 정리는 하지 않는다” 가
여기서는 더 강하게 적용된다.


관측 — 어느 쪽이 처리했는가

장애가 나면 가장 먼저 물어야 할 질문이다.

GET /api/points/1001

응답 헤더에 X-Served-By: legacy | service
로그에 서비스 식별자
추적 ID는 게이트웨이에서 발급해 양쪽에 전달

⚠️ 추적 ID가 양쪽을 관통하지 않으면
공존 기간의 장애 분석이 두 배로 어려워진다.

24장에서 추적 ID를 강조한 이유가
여기서 세 번째로 회수된다.

56장의 장애 대응 Agent도
어느 쪽 로그를 봐야 할지 알아야 한다.

# CLAUDE.md
- 장애 조사 시 X-Served-By 를 먼저 확인한다
- 이관된 도메인은 service/, 나머지는 legacy/ 에서 찾는다

언제 구 코드를 지우는가

전환의 마지막 단계다.

기준은 셋이다.

조건확인 방법
트래픽이 0이다게이트웨이 라우팅 로그, 최소 2주
되돌릴 계획이 없다팀 합의
다른 곳에서 호출하지 않는다내부 호출·배치·스케줄러 확인

⚠️ 세 번째를 자주 놓친다.

34장에서 진입점을 전수 조사하라고 한 이유다.
HTTP 요청은 0인데 배치가 그 코드를 부르고 있을 수 있다.

legacy 의 point 관련 코드를 호출하는 곳을 전부 찾아줘.
HTTP 라우트 말고도 배치·스케줄러·이벤트 리스너·CLI 전부.

지울 때도 한 번에 지우지 않는다.

1단계  라우트만 제거 (코드는 남김) — 2주 관찰
2단계  코드 제거
3단계  테이블 전용 컬럼 정리 (있다면)

🔥 1단계에서 문제가 드러나는 경우가 실제로 있다.


공존 기간의 진척 지표

37장에서 신구조 비율을 지표로 삼았다.
여기서도 같은 방식이 통한다.

이관 진척

도메인      상태          트래픽 비율
point       전환 완료      100% (새)
notification 전환 중       40%
order       경계 정리 중    0%
settlement  미착수         0%

이 표가 있으면 두 가지가 가능해진다.

  • “언제 끝나냐” 에 답할 수 있다
  • 공존 비용이 얼마나 쌓였는지 보인다

두 번째가 중요하다.

전환 중인 도메인이 많을수록
규칙이 복잡해지고 장애 분석이 어려워진다.

동시에 전환 중인 도메인은
둘을 넘기지 않는다.


9부를 마치며

두 장에서 다룬 것은 결국 하나다.

45장  같음을 증명하는 방법
46장  되돌릴 수 있는 상태를 유지하는 방법

번역이 아니다.

🔥 Agent가 번역을 싸게 만든 뒤로
이관 프로젝트의 난이도는 전부 이 둘에 몰려 있다.

그리고 이 둘은 8부에서 경계를 정리해두지 않으면
아예 시작할 수 없다.

7부   무엇이 어떻게 동작하는지 안다
8부   경계를 긋고 떼어낼 수 있게 만든다
9부   실제로 다른 언어로 넘긴다

여기까지가 코드베이스를 바꾸는 이야기다.

10부부터는 그 작업을 반복 가능하게 만드는 도구를 다룬다.


이 장의 핵심

  • 이관은 배포가 아니라 전환이고, 공존 기간은 예상보다 길다
  • 전환 단위가 곧 롤백 단위다
  • 사용자 비율로 나눌 때는 고정 배정이어야 한다
  • 라우팅은 게이트웨이에서 한다 — 코드에 분기를 넣으면 롤백에 배포가 필요해진다
  • 한 테이블의 쓰기는 항상 한 시스템만 담당한다
  • 두 ORM이 같은 테이블을 쓰면 낙관적 락이 무력해진다
  • 공존 기간에는 스키마를 확장하지 않는다 — 롤백 가능성을 지키기 위해서다
  • 그 제약이 이관을 서두르게 만드는 압력이 되고, 그것이 의도된 효과다
  • 구 시스템 CLAUDE.md 에 “새 기능·리팩터링 금지” 를 명시한다
  • 곧 삭제될 코드를 정리하는 것은 낭비다
  • 추적 ID가 양쪽을 관통하지 않으면 장애 분석이 두 배로 어려워진다
  • 구 코드를 지우기 전에 배치·스케줄러 호출을 확인한다
  • 동시에 전환 중인 도메인은 둘을 넘기지 않는다